`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2021/03/24 09:45:13
// Design Name: 
// Module Name: cp0
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module cp0(
    input   logic               clk, res,

    input   logic [ 5: 0]       int_i,
    input   logic [ 7: 0]       adr,
    input   logic [31: 0]       wdata,
    input   logic               we,
    input   logic [ 7: 0]       wadr,
    output  logic [31: 0]       data,

    input   logic               inslot,     //is the instruction inslot?
    input   logic [31: 0]       pc_i,
    input   logic [31: 0]       badadr,
    input   logic               exc_save,
    input   logic               exc_flag,
    input   logic [`EXCT_BUS]   exc_type,
    output  logic               exc_intr_o,

    output  logic [31: 0]       count_o,      //9
    output  logic [31: 0]       status_o,     //12
    output  logic [31: 0]       cause_o,      //13
    output  logic [31: 0]       epc_o,        //14
    
    output  logic               timer_int_o    

    );

    assign exc_intr_o = ((cause_ip & status_im) != 0) &&
                         status_ie  == 1'b1 &&
                         status_exl == 1'b0;

    logic count_eq_compare;
    assign count_eq_compare = (!(count ^ compare) && compare != 32'b0);
    /* ---------------------------> count <--------------------------- */
    logic [31: 0] count;
    
    logic tick;
    always @(posedge clk)   begin
        if(res) 
            tick <= 1'b0;
        else
            tick <= ~tick;        
        
        if(we && wadr == `COUNT)
            count <= wdata;
        else if(tick)
            count <= count + 1'b1;
    end

    /* ---------------------------> compare <--------------------------- */
    logic [31: 0] compare;

    always @(posedge clk)   begin
        if(we && wadr == `COMPARE)
            compare <= wdata;
    end


    /* ---------------------------> status <--------------------------- */
    logic [31: 0] status;

    //bev
    logic status_bev;
    assign status_bev = 1'b1;
    
    //im
    logic [ 7: 0] status_im;
    always @(posedge clk)   begin
        if(we && wadr == `STATUS)
            status_im <= wdata[15: 8];  
    end

    //exl
    logic status_exl;
    always @(posedge clk)   begin
        if(res)
            status_exl <= 1'b0;
        else if(exc_flag)   begin
            case(exc_type)
                `EXCT_INT,  
                `EXCT_RI,   
                `EXCT_OV,   
                `EXCT_SYSC, 
                `EXCT_BP, 
                `EXCT_ADE:
                    status_exl <= 1'b1;  
                `EXCT_ERET: 
                    status_exl <= 1'b0;
            endcase
        end
        else if(we && wadr == `STATUS)
            status_exl <= wdata[1];
    end

    //ie
    logic status_ie;
    always @(posedge clk)   begin
        if(res)
            status_ie <= 1'b0;
        else if(we && wadr == `STATUS)
            status_ie <= wdata[0];
    end

    assign status = {
        9'b0,
        status_bev,
        6'b0,
        status_im,
        6'b0,
        status_exl,
        status_ie
    }; 

    /* ---------------------------> cause <--------------------------- */
    logic [31: 0] cause;

    //bd
    logic cause_bd;
    always @(posedge clk)   begin
        if(res)
            cause_bd <= 1'b0;
        else if(exc_flag && !status_exl)
            cause_bd <= inslot; 
    end

    //ti
    logic cause_ti;
    always @(posedge clk)   begin
        if(res)
            cause_ti <= 1'b0;
        else if(we && wadr == `COMPARE)
            cause_ti <= 1'b0;
        else if(count_eq_compare)
            cause_ti <= 1'b1;
    end

    //ip7-ip2
    logic [ 7: 0] cause_ip;
    always @(posedge clk)   begin
        if(res)
            cause_ip[ 7: 2]     <= 6'b0;
        else begin
            cause_ip[ 7]        <= int_i[ 5] | cause_ti;
            cause_ip[ 6: 2]     <= int_i[ 4: 0];
        end     
    end

    always @(posedge clk)   begin
        if(res)
            cause_ip[ 1: 0]     <= 2'b0;
        else if(we && wadr == `CAUSE)
            cause_ip[ 1: 0]     <= wdata[ 9: 8];
    end

    //exccode
    logic [ 4: 0] cause_exccode;

    always @(posedge clk)   begin
        if(res)
            cause_exccode <= 5'b0;
        else if(exc_flag)   begin
            case(exc_type)
                `EXCT_INT:   cause_exccode <= `EXCC_INT;      
                `EXCT_RI:    cause_exccode <= `EXCC_RI;
                `EXCT_OV:    cause_exccode <= `EXCC_OV;
                `EXCT_SYSC:  cause_exccode <= `EXCC_SYSC;
                `EXCT_BP:    cause_exccode <= `EXCC_BP;
                `EXCT_ADE:   cause_exccode <= exc_save? `EXCC_ADES : `EXCC_ADEL;
            endcase
        end 
    end
 
    assign cause = {
        cause_bd,
        cause_ti,
        14'b0,
        cause_ip,
        1'b0,
        cause_exccode,
        2'b0
    };
    
    /* -----------------------------> epc <----------------------------- */
    logic [31: 0] epc;

    always @(posedge clk)   begin
        if(exc_flag && !status_exl)
            epc <= inslot? pc_i - 4 : pc_i;
        else if(we && wadr == `EPC)
            epc <= wdata;
    end 

    /* ---------------------------> badvaddr <--------------------------- */
    logic [31: 0] badvaddr;

    always @(posedge clk)   begin
        if(exc_flag && exc_type == `EXCT_ADE)
            badvaddr <= badadr;
    end

    /* -------------------------------end------------------------------- */
    assign count_o  = count;
    assign status_o = status;
    assign cause_o  = cause;
    assign epc_o    = epc;

    always_comb begin
        data = 32'b0;
        if(res) begin
            data = 32'b0;
        end
        else begin
            case(adr)
                `BADVADR:   begin
                    data = badvaddr;
                end
                `COUNT:     begin
                    data = count;
                end
                `COMPARE:   begin
                    data = compare;
                end
                `STATUS:    begin
                    data = status;
                end
                `CAUSE:     begin
                    data = cause;
                end
                `EPC:       begin
                    data = epc;
                end
            endcase
        end      
    end

endmodule
